home *** CD-ROM | disk | FTP | other *** search
/ PD ROM 1 / PD ROM Volume I - Macintosh Software from BMUG (1988).iso / Stacks / Updates⁄New / TEXAS for BMUG / C progs / brwsr.2 ƒ / brwsr (for suns⁄vaxen).c < prev    next >
Encoding:
C/C++ Source or Header  |  1988-02-11  |  48.5 KB  |  1,696 lines  |  [TEXT/ttxt]

  1.  
  2. /* transportable version of the browser program -- 871008 ^z
  3.  * version 0.11 -- with fixes to allow for non-ASCII characters
  4.  * in the document/database file....
  5.  *
  6.  * This program lets a user browse through indices created by
  7.  * the qndxr program....
  8.  *
  9.  * Contact Mark Zimmermann, 9511 Gwyndale Drive, Silver Spring, MD  20910
  10.  * ("science (at) nems.arpa, [75066,2044] on CompuServe, 301-565-2166
  11.  * for further information....
  12.  */
  13.  
  14.  
  15. /* commands:
  16.  *
  17.  *    ?            print out helpful info
  18.  *    :            quit program
  19.  *    :fnord       open document file 'fnord' for browsing
  20.  *    >grok        append copy of future output to notes file 'grok'
  21.  *    >            end copying to notes file
  22.  *    'DON.MAC     append comment string 'DON.MAC' to notes file
  23.  *    xyzzy        jump to INDEX item 'XYZZY'
  24.  *    ".012345     take string '.012345' as target for jump
  25.  *    -17          back up 17 lines from here
  26.  *    -            back up one line; same as '-1' command
  27.  *    +23          jump down 23 lines from here
  28.  *    + or <ret>   move down one line; same as '+1' command
  29.  *    .29          print 29 lines from here
  30.  *    =            descend:  INDEX -> CONTEXT -> TEXT display
  31.  *    ^            ascend:  TEXT -> CONTEXT -> INDEX display
  32.  *    *            empty 'working subset' proximity filter (nothing valid)
  33.  *    **           fill 'working subset' (everything valid)
  34.  *    &            add word-neighborhood of current index item to subset
  35.  *    &&           add sentence-neighborhood to subset
  36.  *    &&&          add paragraph-neighborhood to subset
  37.  *    ~            invert (logical NOT) entire working subset
  38.  *    ;            repeat previous command
  39.  *    
  40.  */
  41.  
  42.  
  43. #include <stdio.h>            /* for FILE, printf(), etc. */
  44. #include <strings.h>
  45. #include <ctype.h>
  46.  
  47. /* -----------------header stuff---------------- */
  48.  
  49. /* some definitions for the brwsr program...
  50.  * 870805-07-... ^z
  51.  */
  52.  
  53. /* tell the compiler that we are using Lightspeed C ... mostly so that
  54.  * certain sections of non-transportable code will be activated to
  55.  * compensate for the use of 16-bit int variables in LSC.... */
  56. /* #define LIGHTSPEED */
  57.  
  58. /* KEY_LENGTH is the number of letters we have in each record.... */
  59. #define KEY_LENGTH    28
  60.  
  61. /* CONTEXT_LINE_LENGTH is the total length of a key-word-in-context
  62.  * display line.... */
  63. #define CONTEXT_LINE_LENGTH  75
  64.  
  65. /* CONTEXT_OFFSET is the distance from the start of the context line
  66.  * to where the key word itself begins.... */
  67. #define CONTEXT_OFFSET  30
  68.  
  69. /* STRLEN is the most characters to allow in a line from the
  70.  * file at one time, and the maximum length of a command line.... */
  71. #define STRLEN  256
  72.  
  73. /* CMASK masks off characters to avoid negativity problems with funny
  74.  * non-ASCII symbols... */
  75. #define CMASK  0xFF
  76.  
  77. /* NEIGHBORHOOD is the distance in bytes that defines the proximity
  78.  * neighborhood of a word in the index ... used when defining a
  79.  * subset for proximity searching.... NEIGHBORHOOD * 8 is the
  80.  * compression factor for squeezing the document file down into the
  81.  * array subset[] ...
  82.  * Thus, NEIGHBORHOOD = 32, a good choice, defines a chunkiness of 32
  83.  * characters in making comparisons for proximity determination purposes....
  84.  */
  85. #define NEIGHBORHOOD  32
  86.  
  87. /* WORD_RANGE, SENTENCE_RANGE, and PARAGRAPH_RANGE define how many chunks
  88.  * of size NEIGHBORHOOD on each side of an item that the neighborhood of
  89.  * each type extends.  Values 1, 10, and 100 work pretty well....
  90.  */
  91. #define WORD_RANGE         1
  92. #define SENTENCE_RANGE    10
  93. #define PARAGRAPH_RANGE  100
  94.  
  95. /* define a value to help recognize long operations, for which
  96.  * the user should be warned of a likely delay in response ...
  97.  */
  98. #define GIVE_WARNING  200
  99.  
  100. /* structure of the records in the index key file:
  101.  *    a fixed-length character string kkey, filled out with blanks and
  102.  *        containing the unique alphanumeric 'words' in the document file,
  103.  *        changed to all-capital letters;
  104.  *    a cumulative count of how many total occurrences of words, including
  105.  *        the current one, have happened up to this point in the alphabetized
  106.  *        index.
  107.  *
  108.  *    Obviously, the number of occurrences of the nth word is just the
  109.  *        difference between the nth cumulative count and the (n-1)th
  110.  *        cumulative count.  Also, the (n-1)th cumulative count is a
  111.  *        pointer to the first occurrence of the nth word in the ptr_file
  112.  *        (which holds all the index offset pointers for the document file).
  113.  *
  114.  *    See the index building program "ndxr.c" for further details of the
  115.  *        index structure....
  116.  */
  117. typedef struct
  118.   {
  119.     char kkey[KEY_LENGTH];
  120.     long ccount;
  121.   }  KEY_REC;
  122.  
  123. /* the pointer records are simply long integers, offsets from the start
  124.  * of the document to the indexed words....*/
  125. #define PTR_REC  long
  126.  
  127. /* define the values for the three levels that the user may be browsing
  128.  * on:  INDEX is the display of unique words and their occurrence counts;
  129.  * CONTEXT is the key-word-in-context (KWIC) display; TEXT is the full
  130.  * text of the document.  */
  131. #define INDEX    0
  132. #define CONTEXT  1
  133. #define TEXT     2
  134.  
  135. /* symbolic values for yes/no answers... */
  136. #define TRUE  1
  137. #define FALSE 0
  138.  
  139. /* ---------------prototypes------------------- */
  140.  
  141. /* prototypes for my brwsr functions ...
  142.  * 870805...  ^z
  143.  */
  144.  
  145. #ifdef LIGHTSPEED
  146.  
  147. /* in file_utils.c */
  148. void open_files (char cmd[]);
  149. void close_files (void);
  150. void init_items (void);
  151.  
  152. /* in brwsr.c */
  153. void main (void);
  154.  
  155. /* in show_items.c */
  156. void show_current_item (void);
  157. void show_current_index_item (void);
  158. void show_current_context_item (void);
  159. void show_current_text_item (void);
  160.  
  161. /* in brws_utils.c */
  162. void get_key_rec (KEY_REC *recp, long n);
  163. PTR_REC get_ptr_rec (long n);
  164. long start_of_line (long p);
  165. long next_line (long p);
  166. void beep (void);
  167.  
  168. /* in do_help/files.c */
  169. void do_help (void);
  170. void do_open (char cmd[]);
  171. void do_redirection (char cmd[]);
  172. void do_comments (char cmd[]);
  173.  
  174. /* in do_moves_etc.c */
  175. void do_move (char cmd[]);
  176. int make_move (long n);
  177. int move_in_text (long move);
  178. int make_subindex_move (long move);
  179.  
  180. /* in do_targets.c */
  181. void do_descend (void);
  182. void do_ascend (void);
  183. void do_target_jump (char cmd[]);
  184. int zstrcmp (char *s1, char *s2);
  185. void init_target_item (KEY_REC *rp, char cmd[]);
  186. void do_multiprint (char cmd[]);
  187.  
  188. /* in do_subset.c */
  189. void do_make_subset (char cmd[]);
  190. void create_subset (void);
  191. void destroy_subset (void);
  192. void do_invert_subset (void);
  193. void do_add_neighborhood (char cmd[]);
  194. void set_neighborhood_bits (int n);
  195. void set_subset_bit (long offset);
  196. int get_subset_bit (long offset);
  197. long count_subset_recs (KEY_REC *this_rec, KEY_REC *prev_rec);
  198.  
  199. #endif
  200.  
  201. /* ------------------------main program-------------- */
  202.  
  203.  
  204. /* First, define a few external variables to hold:
  205.  *    - pointers to the document, key, pointer, and notes files;
  206.  *    - numbers indicating the current and the minimum/maximum values for:
  207.  *        INDEX item (one line for each unique word in the document);
  208.  *        CONTEXT item (one line for each occurrence of chosen INDEX word);
  209.  *        TEXT item (byte offset from start of file to current line).
  210.  *    - the actual KEY_RECs corresponding to the current INDEX item and
  211.  *        the INDEX item just before that one
  212.  *    - the subset array pointer used for proximity searching, the flag
  213.  *        empty_subset, and the count of how many current records are in
  214.  *        the working subset subset_rec_count
  215.  *    - the level of user operations:
  216.  *        0     means browsing the INDEX
  217.  *        1    means browsing the CONTEXT (key-word-in-context = KWIC)
  218.  *        2    means browsing the TEXT
  219.  *
  220.  * These items are referred to by various routines at scattered
  221.  * locations, and it seems easiest to let them reside in external
  222.  * variables....
  223.  */
  224.  
  225. FILE *doc_file = NULL, *key_file = NULL, *ptr_file = NULL,
  226.         *notes_file = NULL;
  227. long current_item[3] = {0, 0, 0};
  228. long min_item[3] = {0, 0, 0};
  229. long max_item[3] = {0, 0, 0};
  230. KEY_REC this_rec, prev_rec;
  231. char *subset = NULL;
  232. int empty_subset = TRUE;
  233. long subset_rec_count = 0;
  234. int level = INDEX;
  235.  
  236. void main()
  237.   {
  238.     char cmd[STRLEN], prevcmd[STRLEN], *gets(), *strcpy();
  239.     void do_help(), do_open(), do_redirection(), do_comments(), do_move(),
  240.         do_make_subset(), do_invert_subset(), do_add_neighborhood(),
  241.         do_descend(), do_ascend(), do_target_jump(), do_multiprint(),
  242.         close_files();
  243.     
  244.     prevcmd[0] = '\0';
  245.     printf ("Greetings, program! ... type '?' for help.\n");
  246.     printf ("Browser version 0.11 by ^z - 871007\n");
  247.     
  248.     while (gets (cmd))
  249.       {
  250.          do_cmd:
  251.          switch (cmd[0])
  252.           {
  253.             case '?':
  254.                   do_help ();
  255.                   break;
  256.             case '\0':
  257.                 strcpy (cmd, "+");
  258.                 /* <return> is just + */
  259.             case '+':
  260.                 /* + or - commands are both moves */
  261.             case '-':
  262.                 do_move (cmd);
  263.                 break;
  264.             case ':':
  265.                 do_open (cmd);
  266.                 break;
  267.             case '>':
  268.                 do_redirection (cmd);
  269.                 break;
  270.             case '\'':
  271.                 do_comments (cmd);
  272.                 break;
  273.             case '=':
  274.                 do_descend ();
  275.                 break;
  276.             case '^':
  277.                 do_ascend ();
  278.                 break;
  279.             case '.':
  280.                 do_multiprint (cmd);
  281.                 break;
  282.             case '*':
  283.                 do_make_subset (cmd);
  284.                 break;
  285.             case '&':
  286.                 do_add_neighborhood (cmd);
  287.                 break;
  288.             case '~':
  289.                 do_invert_subset ();
  290.                 break;
  291.             case ';':
  292.                 strcpy (cmd, prevcmd);
  293.                 goto do_cmd;
  294.                 break;
  295.             case '"':
  296.                 do_target_jump (cmd + 1);
  297.                 break;
  298.             default:
  299.                 do_target_jump (cmd);
  300.                 break;
  301.           }
  302.         strcpy (prevcmd, cmd);
  303.       }
  304.   }
  305.  
  306.  
  307. /* ---------------------brws_utils.c----------------- */
  308.  
  309. /* miscellaneous utility functions for helping the higher-level routines
  310.  * browse around in indexed files....
  311.  * 870805-13-...  ^z
  312.  */
  313.  
  314. /* function to get the 'n'th KEY_REC from key_file and store it in the
  315.  * KEY_REC space pointed to by recp ... note that if an illegal value
  316.  * of 'n' is requested, a 'null' KEY_REC is produced....
  317.  */
  318.  
  319. void get_key_rec (recp, n)
  320.   KEY_REC *recp;
  321.   register long n;
  322.   {
  323.     extern FILE *key_file;
  324.     extern long min_item[], max_item[];
  325.     char *strncpy();
  326.     void beep();
  327.     
  328.     if (n < min_item[INDEX] || n > max_item[INDEX])
  329.       {
  330.         strncpy ((char *)recp->kkey, "                    ",
  331.                     KEY_LENGTH);
  332.         recp->ccount = 0;
  333.         return;
  334.       }
  335.     
  336.     if (fseek (key_file, sizeof (KEY_REC) * n, 0) != 0)
  337.       {
  338.         beep ();
  339.         printf ("Fatal error in fseek() getting key record #%ld!\n", n);
  340.         exit();
  341.       }
  342.     
  343.     if (fread ((char *)recp, sizeof (KEY_REC), 1, key_file) == 0)
  344.       {
  345.         beep ();
  346.         printf ("Fatal error in fread() getting key record #%ld!\n", n);
  347.         exit();
  348.       }
  349.   }
  350.  
  351.  
  352. /* routine to get the nth pointer record (a long integer) from ptr_file;
  353.  * the pointer record gives the actual offset in the document file of
  354.  * the nth word in the expanded index....
  355.  */
  356.  
  357. PTR_REC get_ptr_rec (n)
  358.   register long n;
  359.   {
  360.     PTR_REC p;
  361.     extern FILE *ptr_file;
  362.     void beep();
  363.     
  364.     if (fseek (ptr_file, sizeof (PTR_REC) * n, 0) != 0)
  365.       {
  366.         beep ();
  367.         printf ("Fatal error in fseek() getting pointer record #%ld!\n", n);
  368.         exit();
  369.       }
  370.     
  371.     if (fread ((char *)&p, sizeof (PTR_REC), 1, ptr_file) == 0)
  372.       {
  373.         beep ();
  374.         printf ("Fatal error in fread() getting pointer record #%ld!\n", n);
  375.         exit();
  376.       }
  377.     
  378.     return (p);
  379.   }
  380.  
  381.  
  382. /* Move backwards in document file to find the start of the line of text
  383.  * which has offset p (from start of file) somewhere in that line.
  384.  * However, don't go back beyond STRLEN characters at a time... that
  385.  * is, put a ceiling of STRLEN on the maximum 'length of a line', to
  386.  * avoid pathological input files with no \n's....
  387.  * Do the work by grabbing a buffer full and then scanning back in that,
  388.  * rather than by laboriously lseek() and fgetc() back one character at
  389.  * a time....
  390.  */
  391.  
  392. long start_of_line (p)
  393.   register long p;
  394.   {
  395.     extern FILE *doc_file;
  396.     char buf[STRLEN];
  397.     register long i;
  398.     register int n;
  399.     void beep();
  400.  
  401.     if (p <= 0)
  402.         return (0L);
  403.  
  404.     i = p - STRLEN;
  405.     i = ((i < 0) ? 0 : i);
  406.     if (fseek (doc_file, i, 0) != 0)
  407.       {
  408.         beep ();
  409.         printf ("Fatal error in fseek() backing up in document!\n");
  410.         exit();
  411.       }
  412.     if ((n = fread (buf, sizeof (char), p - i, doc_file)) == 0)
  413.       {
  414.         beep ();
  415.         printf ("Fatal error in fread() backing up in document!\n");
  416.         exit();
  417.       }
  418.  
  419.     while (--n >= 0 && buf[n] != '\n');
  420.     
  421.     return (i + n + 1);
  422.   }
  423.  
  424.  
  425. /* scan forward until reaching the beginning of the next line of text;
  426.  * if at or beyond the end of the file, return a pointer to the last
  427.  * character in the file ...
  428.  * Don't go beyond STRLEN characters, however, in scanning forward....
  429.  */
  430.  
  431. long next_line (p)
  432.   long p;
  433.   {
  434.     register int c, n;
  435.     extern long max_item[];
  436.     void beep();
  437.     
  438.     if (p >= max_item[TEXT])
  439.         return (max_item[TEXT]);
  440.     
  441.     if (fseek (doc_file, p, 0) != 0)
  442.       {
  443.         beep ();
  444.         printf ("Fatal error in fseek() scanning forward in document!\n");
  445.         exit();
  446.       }
  447.     
  448.     for (n = 0; n < STRLEN; ++n)
  449.         if ((c = fgetc (doc_file)) == EOF || c == '\n')
  450.             break;
  451.     
  452.     return (ftell (doc_file));
  453.   }
  454.  
  455.  
  456. /* paging James Bond ....
  457.  */
  458.  
  459. void beep ()
  460.   {
  461.     putchar ('\007');
  462.   }
  463.  
  464.  
  465. /* ---------------------do_help/files.c---------------- */
  466.  
  467.  
  468.  
  469. /* function to print out helpful information -- a command summary
  470.  * for the user....
  471.  */
  472.  
  473. void do_help ()
  474.   {
  475.     printf ("  ?        print this help\n");
  476.     printf ("  :        quit the program\n");
  477.     printf ("  :fnord   open document file 'fnord' for browsing\n");
  478.     printf ("  >grok    open notes file 'grok' and append output\n");
  479.     printf ("  >        close notes file\n");
  480.     printf ("  'DON.MAC append comment 'DON.MAC' to notes file\n");
  481.     printf ("  xyzzy    jump to 'XYZZY' in INDEX\n");
  482.     printf ("  \".123    jump to '.123' in INDEX\n");
  483.     printf ("  -17      back up 17 lines\n");
  484.     printf ("  -        back up one line\n");
  485.     printf ("  +0       print current line\n");
  486.     printf (" <return>  move down one line\n");
  487.     printf ("  +23      move down 23 lines\n");
  488.     printf ("  =        INDEX --> CONTEXT --> TEXT\n");
  489.     printf ("  ^        INDEX <-- CONTEXT <-- TEXT\n");
  490.     printf ("  .9       move down and print out 9 lines\n");
  491.     printf ("  *        create an empty working subset\n");
  492.     printf ("  **       fill the working subset (complete database)\n");
  493.     printf ("  &        add word-neighborhood to subset\n");
  494.     printf ("  &&       add sentence-neighborhood to subset\n");
  495.     printf ("  &&&      add paragraph-neighborhood to subset\n");
  496.     printf ("  ~        invert (logical NOT) entire working subset\n");
  497.     printf ("  ;        repeat previous command\n");
  498.   }
  499.  
  500.  
  501. /* function to handle a ":" command, to either quit the program or to
  502.  * open a new database for browsing.... display some amusing data
  503.  * about the file that is opened, if successful (total length in bytes,
  504.  * total number of words, and number of unique words in the file)...
  505.  */
  506.  
  507. void do_open (cmd)
  508.   char cmd[];
  509.   {
  510.     char qcmd[STRLEN];
  511.     extern FILE *doc_file;
  512.     extern long max_item[];
  513.     void destroy_subset (), open_files (), init_items ();
  514.     
  515.     if (cmd[1] == '\0')
  516.       {
  517.         printf ("Quit?\n");
  518.         gets (qcmd);
  519.         if (qcmd[0] == 'y' || qcmd[0] == 'Y')
  520.           {
  521.             printf ("Good-bye ... ^z\n");
  522.             exit ();
  523.           }
  524.         else
  525.           {
  526.             printf ("Cancelled!\n");
  527.             return;
  528.           }
  529.       }
  530.     
  531.     destroy_subset ();
  532.     open_files (cmd);
  533.     if (doc_file == NULL)
  534.         return;
  535.     init_items ();
  536.     printf ("File \"%s\" contains:\n", cmd + 1);
  537.     printf ("%9ld characters\n%9ld total words\n%9ld unique words\n",
  538.         max_item[TEXT] + 1, max_item[CONTEXT] + 1, max_item[INDEX] + 1);
  539.   }
  540.  
  541.  
  542. /* function to open or close a notes file ... copies of browser
  543.  * program output gets appended to that notes file as long as it
  544.  * is open, and the user may add comment strings with the '"' command ....
  545.  */
  546.  
  547. void do_redirection (cmd)
  548.   char cmd[];
  549.   {
  550.      extern FILE *notes_file;
  551.      FILE *fopen ();
  552.      void beep();
  553.      
  554.     if (notes_file != NULL)
  555.          fclose (notes_file);
  556.      
  557.      if (cmd[1] == '\0')
  558.          return;
  559.  
  560.      if ((notes_file = fopen (cmd + 1, "a")) == NULL)
  561.        {
  562.         beep ();
  563.         printf ("Error opening notes file \"%s\"!\n", cmd + 1);
  564.         return;
  565.        }
  566.   }
  567.  
  568.  
  569. /* function to append a comment line to the notes_file... does the job
  570.  * for the '"' command....
  571.  */
  572.  
  573. void do_comments (cmd)
  574.   char cmd[];
  575.   {
  576.      extern FILE *notes_file;
  577.      void beep();
  578.      
  579.      if (notes_file == NULL)
  580.        {
  581.         beep ();
  582.         printf ("No notes file open!\n");
  583.         return;
  584.        }
  585.      else
  586.         fprintf (notes_file, "%s\n", cmd + 1);
  587.   }
  588.   
  589.  /* -------------------do_moves.c---------------------- */
  590.  
  591.  
  592.  
  593. /* function to interpret a command to move around in the current level
  594.  * of the browser display ... handles "+", "-", and "<return>" ... calls
  595.  * routine make_move() to do the real work....
  596.  */
  597.  
  598. void do_move (cmd)
  599.   char cmd[];
  600.   {
  601.     long move;
  602.     extern FILE *doc_file;
  603.     char *strcat();
  604.     void beep(), show_current_item();
  605.     
  606.     if (doc_file == NULL)
  607.       {
  608.         beep ();
  609.         printf ("No file open!\n");
  610.         return;
  611.       }
  612.     
  613.     if (cmd[1] == '\0')
  614.         strcat (cmd, "1");
  615.     move = atol (cmd);
  616.     make_move (move);
  617.     show_current_item ();
  618.   }
  619.  
  620.  
  621. /* function to do the actual moving around in the INDEX or CONTEXT or
  622.  * TEXT displays .... Safety features prevent user from going
  623.  * off the end of the file in either direction....
  624.  * make_move() returns FALSE if it fails to completely execute the move
  625.  * (i.e., if the move would have run off either end of the file) and
  626.  * beeps at the user ... it returns TRUE if the move succeeds....
  627.  */
  628.  
  629. int make_move (n)
  630.   long n;
  631.   {
  632.     int r;
  633.     PTR_REC get_ptr_rec ();
  634.     extern long current_item[], min_item[], max_item[];
  635.     extern int level;
  636.     extern char *subset;
  637.     void beep();
  638.     
  639.     r = TRUE;
  640.     switch (level)
  641.       {
  642.         case INDEX:
  643.             current_item[INDEX] += n;
  644.             if (current_item[INDEX] < min_item[INDEX])
  645.               {
  646.                 current_item[INDEX] = min_item[INDEX];
  647.                 r = FALSE;
  648.               }
  649.             if (current_item[INDEX] > max_item[INDEX])
  650.               {
  651.                 current_item[INDEX] = max_item[INDEX];
  652.                 r = FALSE;
  653.               }
  654.             break;
  655.  
  656.         case CONTEXT:
  657.             if (subset == NULL)
  658.               {
  659.                 current_item[CONTEXT] += n;
  660.                 if (current_item[CONTEXT] < min_item[CONTEXT])
  661.                   {
  662.                     current_item[CONTEXT] = min_item[CONTEXT];
  663.                     r = FALSE;
  664.                   }
  665.                 if (current_item[CONTEXT] > max_item[CONTEXT])
  666.                   {
  667.                     current_item[CONTEXT] = max_item[CONTEXT];
  668.                     r = FALSE;
  669.                   }
  670.               }
  671.             else
  672.                 r = make_subindex_move (n);
  673.  
  674.             current_item[TEXT] = get_ptr_rec (current_item[CONTEXT]);
  675.             break;
  676.  
  677.         case TEXT:
  678.             r = move_in_text (n);
  679.             break;
  680.       }
  681.     
  682.     if (r == FALSE)
  683.         beep ();
  684.     return (r);
  685.   }
  686.  
  687.  
  688. /* move up or down the chosen number of lines of text, and put the result
  689.  * into current_item[TEXT] ... return FALSE if attempted move would have
  690.  * run off the end of the file, non-zero if the move was completed without
  691.  * error....
  692.  */
  693.  
  694. int move_in_text (move)
  695.   register long move;
  696.   {
  697.     int result;
  698.     register long loc;
  699.     extern long current_item[];
  700.     
  701.     result = TRUE;
  702.     loc = current_item[TEXT];
  703.     
  704.     if (move < 0)
  705.         for ( ; move < 0; ++move)
  706.           {
  707.             if (loc <= 0)
  708.               {
  709.                 result = FALSE;
  710.                 break;
  711.               }
  712.             loc = start_of_line (loc - 1);
  713.           }
  714.             
  715.     else if (move > 0)
  716.         for ( ; move > 0; --move)
  717.           {
  718.             loc = next_line (loc);
  719.             if (loc >= max_item[TEXT])
  720.               {
  721.                 result = FALSE;
  722.                 loc = start_of_line (max_item[TEXT]);
  723.                 break;
  724.               }
  725.           }
  726.     
  727.     current_item[TEXT] = loc;
  728.     return (result);
  729.   }
  730.  
  731.  
  732. /* make a move in the working subindex ... must just step along in
  733.  * whichever direction is desired until either hitting the end (in
  734.  * which case the last valid item found in the subindex should be
  735.  * returned as the current one) or finishing the requested move.
  736.  * Return FALSE if hit a wall, TRUE otherwise....
  737.  *
  738.  * This routine is only called when level == CONTEXT ... since
  739.  * in TEXT level we are just moving around in the full text of
  740.  * the document file, and in the INDEX level we count and display
  741.  * even items with zero occurrences in the subindex...
  742.  *
  743.  * This routine is only called when subset != NULL, since if there
  744.  * is no working subset the move becomes trivial arithmetic.
  745.  *
  746.  * This routine is only called when either current_item[CONTEXT]
  747.  * is a good item in the subset already, or when there is a certainty
  748.  * that a good item will be found in the subsequent scan (specifically,
  749.  * when scanning down to find the first good item when called from
  750.  * do_descend () ....).
  751.  */
  752.  
  753. int make_subindex_move (move)
  754.   long move;
  755.   {
  756.     register long i, last_good_item;
  757.     int result;
  758.     PTR_REC get_ptr_rec ();
  759.     extern long current_item[], min_item[], max_item[];
  760.     extern char *subset;
  761.  
  762.     result = TRUE;
  763.     last_good_item = current_item[CONTEXT];
  764.  
  765.     if (move >= 0)
  766.       {
  767.         for (i = 0; i < move; ++i)
  768.           {
  769.             while (++current_item[CONTEXT] <= max_item[CONTEXT] &&
  770.                 ! get_subset_bit ((long)
  771.                     get_ptr_rec (current_item[CONTEXT])));
  772.             if (current_item[CONTEXT] > max_item[CONTEXT])
  773.                 break;
  774.             last_good_item = current_item[CONTEXT];
  775.           }
  776.         if (current_item[CONTEXT] > max_item[CONTEXT])
  777.           {
  778.             result = FALSE;
  779.             current_item[CONTEXT] = last_good_item;
  780.           }
  781.       }
  782.     else
  783.       {
  784.         for (i = 0; i > move; --i)
  785.           {
  786.             while (--current_item[CONTEXT] >= min_item[CONTEXT] &&
  787.                 ! get_subset_bit ((long)
  788.                     get_ptr_rec (current_item[CONTEXT])));
  789.             if (current_item[CONTEXT] < min_item[CONTEXT])
  790.                 break;
  791.             last_good_item = current_item[CONTEXT];
  792.           }
  793.         if (current_item[CONTEXT] < min_item[CONTEXT])
  794.           {
  795.             result = FALSE;
  796.             current_item[CONTEXT] = last_good_item;
  797.           }
  798.       }
  799.     return (result);
  800.   }
  801.   
  802.  /* -------------------do_subset.c------------------- */
  803.  
  804. /* words to handle subset (proximity filter) tasks....
  805.  * ^z -- 870810-13-....
  806.  *
  807.  * Subset (or subindex) restrictions are useful in doing searches for
  808.  * combinations of terms, phrases, etc., where the terms are themselves
  809.  * common words.  Subsets also become indispensible when working in
  810.  * very large databases, where even slightly-exotic terms occur too many
  811.  * times for convenient browsing through their CONTEXT displays.
  812.  *
  813.  * When a subset has been defined, then instead of showing an INDEX
  814.  * item as something like:
  815.  *      3141 UNIX
  816.  * that item is displayed with a count of how many occurrences are
  817.  * 'valid' in addition to the total number of occurrences:
  818.  *   27/3141 UNIX
  819.  * Thus, in this case only 27 out of the 3141 appearances of the term
  820.  * 'UNIX' happened to be in the neighborhood of the set of words chosen
  821.  * by the user to define the current working subset.
  822.  * 
  823.  * The 'neighborhood' (a half dozen words or so on each side) of a chosen
  824.  * index term is added (logical OR) into the working subset by giving
  825.  * the '&' command when that term's INDEX display is the current item.
  826.  * If a larger neighborhood (half a dozen sentences or so each way) is
  827.  * preferable, use the '&&' command; for a still larger neighborhood
  828.  * (a few paragraphs on each side), use the '&&&' command.
  829.  *
  830.  *
  831.  * IMPLEMENTATION NOTES:
  832.  *
  833.  * Subest searching is handled by an array of flag bits -- one bit
  834.  * for each chunk of NEIGHBORHOOD bytes in the original text.  Bits are
  835.  * held in the array subset[]; they are zero for 'invalid' regions
  836.  * of the original document, and one for 'valid' regions that have
  837.  * been defined by proximity to index terms selected by the user.
  838.  *
  839.  * A value of 32 for NEIGHBORHOOD seems to work very well for defining
  840.  * proximity.  The compression factor from the original document to the
  841.  * array subset[] is NEIGHBORHOOD*8 = 256 in that case.  So, even for a
  842.  * document that is tens of megabytes long, the subset[] array only
  843.  * requires a few hundred kB and it is quite feasible to hold it in
  844.  * memory.
  845.  *
  846.  * The presence of a subset is signalled when the global array pointer
  847.  * 'subset' is non-NULL ... in which case show_current_index_item,
  848.  * make_move, and other words modify their behavior in order to
  849.  * reflect the chosen subset.  If the subset is completely
  850.  * empty, after having just been created or flushed, then the
  851.  * global variable empty_subset is nonzero (true) and short-cuts
  852.  * are taken in counting and displaying words....
  853.  *
  854.  * Consider a single occurrence of a word.  Suppose we want to add the
  855.  * immediate neighborhood of that word to our working subset.  To avoid
  856.  * edge-effects due to the chunkiness of the definition of neighborhood,
  857.  * the routine to set bits in subset[] actually sets 2*WORD_RANGE+1 bits:
  858.  * the bit corresponding to the chunk wherein the chosen word falls,
  859.  * and WORD_RANGE extra bits on each side.
  860.  *
  861.  * Thus, for the choice WORD_RANGE = 1, any index item which is
  862.  * within NEIGHBORHOOD (=32) bytes of our chosen word is certain
  863.  * to be included in the proximity subset; any item which occurs
  864.  * at least 2*NEIGHBORHOOD (=64) bytes away is certain NOT to be
  865.  * included, and items in the intermediate range have a
  866.  * linearly-decreasing probability of false inclusion.  The choice
  867.  * of 32 for NEIGHBORHOOD and 1 for WORD_RANGE therefore gives
  868.  * essentially all items within half a dozen or so words
  869.  * (average word length is 5-6 letters) of the term used to
  870.  * define the subset....
  871.  *
  872.  * In extensive experimentation with the MacForth-based Browser v.244+
  873.  * program on the Macintosh, the above definition of proximity seemed
  874.  * to be by far the most useful one in real life.  It is also quite
  875.  * straightforward to implement with good efficiency.  In addition
  876.  * to the 'word'-neighborhood proximity (within WORD_RANGE chunks),
  877.  * it is occasionally convenient to add a somewhat larger
  878.  * neighborhood to the working subset -- corresponding to proximity
  879.  * within a few sentences or even a few paragraphs.  To do that,
  880.  * the && (sentence-proximity) and &&& (paragraph-proximity) commands
  881.  * simply set more bits on either side of the word's location in
  882.  * subset[].  Specifically, && sets SENTENCE_RANGE bits on each side,
  883.  * and &&& sets PARAGRAPH_RANGE bits on each side.  Values of 10 and 100
  884.  * respectively for those parameters seem to work quite well.
  885.  */
  886.  
  887.  
  888.  
  889. /* Handle the * command to empty out the working subset before
  890.  * beginning to do proximity-filtered searching (that is, to create
  891.  * the array subset[] in empty form). 
  892.  * Also handle the ** command to destroy subset[] (which makes it
  893.  * seem as if it is "full" from the user's viewpoint)
  894.  *
  895.  * Force the user back to INDEX level when the working subindex
  896.  * is emptied or filled, to avoid inconsistencies in moving around
  897.  * the CONTEXT level....
  898.  */
  899.  
  900. void do_make_subset (cmd)
  901.   char cmd[];
  902.   {
  903.     void beep (), create_subset (), destroy_subset (),
  904.             show_current_item ();
  905.     extern int level;
  906.     
  907.     if (cmd[1] == '\0')
  908.         create_subset ();
  909.     else if (cmd[1] == '*')
  910.         destroy_subset ();
  911.     else
  912.         beep ();
  913.     
  914.     level = INDEX;
  915.     show_current_item ();
  916.   }
  917.  
  918.  
  919. /* Do the job of creating the empty array subset[] ... note that if
  920.  * running on a machine with 16-bit int variables such as Lightspeed C
  921.  * on the Mac, the calloc() function won't work for files bigger
  922.  * than 16 MB or so (for NEIGHBORHOOD = 32) ....must modify this
  923.  * to use clalloc(), the non-ANSI standard function for allocating
  924.  * and clearing bigger memory chunks.  Hence, the #ifdef LIGHTSPEED
  925.  * lines below....
  926.  *
  927.  * Note that there is no need to provide a "No file open!" error msg
  928.  * since show_current_item() in calling function will do that for
  929.  * us....
  930.  */
  931.  
  932. void create_subset ()
  933.   {
  934.     extern char* subset;
  935.     extern FILE *doc_file;
  936.     extern int empty_subset;
  937.     extern long max_item[];
  938. #ifdef LIGHTSPEED
  939.     char *clalloc ();
  940. #else
  941.     char *calloc ();
  942. #endif
  943.     void beep (), destroy_subset ();
  944.     
  945.     if (doc_file == NULL)
  946.       return;
  947.  
  948.     destroy_subset ();
  949. #ifdef LIGHTSPEED
  950.     if ((subset = clalloc ((unsigned long)(1 +
  951.             max_item[TEXT]/(NEIGHBORHOOD*8)), (long)sizeof(char))) == NULL)
  952. #else    
  953.     if ((subset = calloc ((unsigned int)(1 +
  954.             max_item[TEXT]/(NEIGHBORHOOD*8)), sizeof(char))) == NULL)
  955. #endif
  956.       {
  957.         beep ();
  958.         printf ("Not enough memory for subset!\n");
  959.         return;
  960.       }
  961.     
  962.     empty_subset = TRUE;
  963.   }
  964.  
  965.  
  966. /* routine to destroy a subset (used in response to a ** command,
  967.  * or when changing over to a new file with a : command, or when about
  968.  * to create a new empty subset inside the * command)
  969.  */
  970.  
  971. void destroy_subset ()
  972.   {
  973.     extern char *subset;
  974.     
  975.     if (subset != NULL)
  976.         free (subset);
  977.     subset = NULL;
  978.   }
  979.  
  980.  
  981. /* routine to invert the working subindex (response to a ~ command) --
  982.  * perform a logical NOT on the entire set, so that what was once a
  983.  * member of the set becomes a non-member, and vice versa....
  984.  *
  985.  * Must set empty_subset FALSE since we don't a priori know that the
  986.  * resulting set is empty or full or what....
  987.  *
  988.  * As with * and ** commands, kick the user back to the INDEX level
  989.  * when this command is executed, for safety's sake....
  990.  */
  991.  
  992. void do_invert_subset ()
  993.   {
  994.     register long i, lim;
  995.     extern char *subset;
  996.     extern long max_item[];
  997.     extern int empty_subset;
  998.     void beep();
  999.  
  1000.     if (doc_file == NULL)
  1001.       {
  1002.         beep ();
  1003.         printf ("No file open!\n");
  1004.         return;
  1005.       }
  1006.     if (subset == NULL)
  1007.       {
  1008.         beep ();
  1009.         printf ("No subset!\n");
  1010.         return;
  1011.       }
  1012.  
  1013.     lim = max_item[TEXT]/(NEIGHBORHOOD*8);
  1014.     for (i = 0; i <= lim; ++i)
  1015.         subset[i] = ~subset[i];
  1016.  
  1017.     empty_subset = FALSE;
  1018.     level = INDEX;
  1019.     show_current_item ();
  1020.   }
  1021.   
  1022.  
  1023. /* handle the '&' command to add the current word's neighborhood to
  1024.  * the working subset:
  1025.  *    &    adds a few-words neighborhood
  1026.  *    &&    adds a few-sentences neighborhood
  1027.  *    &&&    adds a few-paragraphs neighborhood
  1028.  *
  1029.  * Note that instead of calling show_current_item() to finish up
  1030.  * this function, since we know that every occurrence of the
  1031.  * current item will be in the working subset we can save time
  1032.  * and avoid re-counting them all ... hence, the final lines
  1033.  * of this routine
  1034.  */
  1035.  
  1036. void do_add_neighborhood (cmd)
  1037.   char cmd[];
  1038.   {
  1039.     int nsize;
  1040.     extern int level;
  1041.     extern char *subset;
  1042.     extern FILE *doc_file, *notes_file;
  1043.     void beep (), set_neighborhood_bits (), get_key_rec ();
  1044.     extern KEY_REC this_rec, prev_rec;
  1045.     extern long current_item[], subset_rec_count;
  1046.  
  1047.     if (doc_file == NULL)
  1048.       {
  1049.         beep ();
  1050.         printf ("No file open!\n");
  1051.         return;
  1052.       }
  1053.     if (subset == NULL)
  1054.       {
  1055.         beep ();
  1056.         printf ("No subset!\n");
  1057.         return;
  1058.       }
  1059.  
  1060.     nsize = WORD_RANGE;
  1061.     if (cmd[1] == '&')
  1062.       {
  1063.         nsize = SENTENCE_RANGE;
  1064.         if (cmd[2] == '&')
  1065.             nsize = PARAGRAPH_RANGE;
  1066.       }
  1067.     
  1068.     level = INDEX;
  1069.     set_neighborhood_bits (nsize);
  1070.     empty_subset = FALSE;
  1071.     get_key_rec (&prev_rec, current_item[INDEX] - 1);
  1072.     get_key_rec (&this_rec, current_item[INDEX]);
  1073.     subset_rec_count = this_rec.ccount - prev_rec.ccount;
  1074.     printf ("%6ld/%-6ld %.28s\n", subset_rec_count, subset_rec_count,
  1075.                 this_rec.kkey);
  1076.     if (notes_file != NULL)
  1077.         fprintf (notes_file, "%6ld/%-6ld %.28s\n", subset_rec_count,
  1078.                     subset_rec_count, this_rec.kkey);
  1079.   }
  1080.  
  1081.  
  1082. /* function to set n bits on each side of each current index term in the
  1083.  * subset[] array ... note that it is important to set min_item[CONTEXT]
  1084.  * and max_item[CONTEXT] values before using them, since they are only
  1085.  * set ordinarily when descending ('=' command) into the CONTEXT level...
  1086.  * values of prev_rec and this_rec are set every time an index item
  1087.  * is displayed, so they will be all right for us already....
  1088.  */
  1089.  
  1090. void set_neighborhood_bits (n)
  1091.   register int n;
  1092.   {
  1093.     register long i;
  1094.     register PTR_REC j, j0, j1;
  1095.     extern long min_item[], max_item[];
  1096.     extern KEY_REC prev_rec, this_rec;
  1097.     PTR_REC get_ptr_rec ();
  1098.     void set_subset_bit ();
  1099.     
  1100.     min_item[CONTEXT] = prev_rec.ccount;
  1101.     max_item[CONTEXT] = this_rec.ccount - 1;
  1102.     
  1103.     if ((this_rec.ccount-prev_rec.ccount) * n > GIVE_WARNING)
  1104.         printf ("Please wait (%ld bits to set)...\n",
  1105.             (this_rec.ccount - prev_rec.ccount) * n * 2 + 1);
  1106.  
  1107.     for (i = min_item[CONTEXT]; i <= max_item[CONTEXT]; ++i)
  1108.       {
  1109.         j = get_ptr_rec (i);
  1110.         j0 = j - n * NEIGHBORHOOD;
  1111.         if (j0 < min_item[TEXT])
  1112.             j0 = min_item[TEXT];
  1113.         j1 = j + n * NEIGHBORHOOD;
  1114.         if (j1 > max_item[TEXT])
  1115.             j1 = max_item[TEXT];
  1116.         for (j = j0; j <= j1; j += NEIGHBORHOOD)
  1117.             set_subset_bit (j);
  1118.       }
  1119.   }
  1120.  
  1121.  
  1122. /* function to set a bit in the array subset[] for a chosen character
  1123.  * offset from the start of the file ... just convert the offset into
  1124.  * byte and bit numbers and then "OR" the 1 into that slot....
  1125.  *
  1126.  * (An attempt to optimize for the specific value NEIGHBORHOOD = 32,
  1127.  * using & and >> in place of % and /, did not yield a significant
  1128.  * improvement in speed... ^z 870813)
  1129.  */
  1130.  
  1131. void set_subset_bit (offset)
  1132.   long offset;
  1133.   {
  1134.     int bit_no;
  1135.     long byte_no;
  1136.     extern char *subset;
  1137.  
  1138.     bit_no = (offset % (8 * NEIGHBORHOOD)) / NEIGHBORHOOD;
  1139.     byte_no = offset / (8 * NEIGHBORHOOD);
  1140.     
  1141.     subset[byte_no] |= 1 << bit_no;
  1142.   }
  1143.  
  1144.  
  1145. /* function to return the value of a bit in the array subset[] for a
  1146.  * chosen character offset from the start of the document file ...
  1147.  * as in set_subset_bit() above, just convert offset into byte and
  1148.  * bit numbers, extract the relevant bit, shift it down, and return
  1149.  * it....
  1150.  *
  1151.  * (An attempt to optimize for the specific value NEIGHBORHOOD = 32,
  1152.  * using & and >> in place of % and /, did not yield a significant
  1153.  * improvement in speed... ^z 870813)
  1154.  */
  1155.  
  1156. int get_subset_bit (offset)
  1157.   long offset;
  1158.   {
  1159.     int bit_no;
  1160.     long byte_no;
  1161.     extern char *subset;
  1162.  
  1163.     bit_no = (offset % (8 * NEIGHBORHOOD)) / NEIGHBORHOOD;
  1164.     byte_no = offset / (8 * NEIGHBORHOOD);
  1165.  
  1166.     return ((subset[byte_no] >> bit_no) & 1);
  1167.   }
  1168.  
  1169.  
  1170. /* function to count and return the number of occurrences of this_rec
  1171.  * in the working subindex (i.e., the number of times the word comes
  1172.  * up with its subset[] bit turned ON) ....
  1173.  */
  1174.  
  1175. long count_subset_recs (this_recp, prev_recp)
  1176.   KEY_REC *this_recp, *prev_recp;
  1177.   {
  1178.     register long i, n;
  1179.     extern int empty_subset;
  1180.     PTR_REC get_ptr_rec ();
  1181.     int get_subset_bit ();
  1182.     
  1183.     if (empty_subset)
  1184.         return (0L);
  1185.  
  1186.     if (this_recp->ccount - prev_recp->ccount > GIVE_WARNING)
  1187.         printf ("Please wait (%ld words to check)...\n",
  1188.                     this_recp->ccount - prev_recp->ccount);
  1189.     
  1190.     for (n = 0, i = prev_recp->ccount; i < this_recp->ccount; ++i)
  1191.         n += get_subset_bit ((long)get_ptr_rec (i));
  1192.     
  1193.     return (n);
  1194.   }
  1195.  
  1196.  
  1197. /* ----------------------do_targets.c----------------- */
  1198.  
  1199. /* some functions to respond to various user inputs...
  1200.  *  870806-13-... ^z
  1201.  */
  1202.  
  1203.  
  1204. /* function to move down a level in the browser hierarchy:
  1205.  *    INDEX --> CONTEXT --> TEXT
  1206.  * includes various safety features against errors, and takes
  1207.  * care of initializations for the level being entered ... also
  1208.  * displays the current line when entering the new level, for the user's
  1209.  * convenience....
  1210.  *
  1211.  * Modified for subset operations, ^z - 870813.  Note that we must check
  1212.  * here to be sure that we are descending into a non-empty subset when
  1213.  * a subset is active and we are descending from INDEX --> CONTEXT;
  1214.  * we also must make sure in that case that we initialize the CONTEXT
  1215.  * display to start with the first good item, not just min_item[CONTEXT].
  1216.  */
  1217.  
  1218. void do_descend ()
  1219.   {
  1220.     extern int level, empty_subset;
  1221.     extern long current_item[], min_item[], max_item[], subset_rec_count;
  1222.     extern KEY_REC this_rec, prev_rec;
  1223.     extern FILE *doc_file;
  1224.     extern char *subset;
  1225.     void beep();
  1226.     
  1227.     if (doc_file == NULL)
  1228.       {
  1229.         beep ();
  1230.         printf ("No file open!\n");
  1231.         return;
  1232.       }
  1233.  
  1234.     if (current_item[INDEX] < 0)
  1235.       {
  1236.         beep ();
  1237.         printf ("No current index item!\n");
  1238.         return;
  1239.       }
  1240.     
  1241.     if (subset != NULL && (empty_subset || subset_rec_count == 0))
  1242.       {
  1243.         beep ();
  1244.         printf ("No items in subset!\n");
  1245.         return;
  1246.       }
  1247.  
  1248.     ++level;
  1249.  
  1250.     if (level == CONTEXT)
  1251.       {
  1252.         min_item[CONTEXT] = prev_rec.ccount;
  1253.         max_item[CONTEXT] = this_rec.ccount - 1;
  1254.         if (subset == NULL)
  1255.             current_item[CONTEXT] = min_item[CONTEXT];
  1256.         else
  1257.           {
  1258.             current_item[CONTEXT] = min_item[CONTEXT] - 1;
  1259.             make_subindex_move (1L);
  1260.           }
  1261.         current_item[TEXT] = get_ptr_rec (current_item[CONTEXT]);
  1262.       }
  1263.  
  1264.     if (level == TEXT)
  1265.         current_item[TEXT] =
  1266.                 start_of_line (current_item[TEXT]);
  1267.  
  1268.     if (level > TEXT)
  1269.       {
  1270.         beep ();
  1271.         printf ("Can't descend past full text display!\n");
  1272.         level = TEXT;
  1273.       }
  1274.  
  1275.     show_current_item ();
  1276.   }
  1277.  
  1278. /* function to move up a level in the browser hierarchy:
  1279.  *       TEXT --> CONTEXT --> INDEX
  1280.  * includes a safety net against going past INDEX, and
  1281.  * displays the current line when entering the new level...
  1282.  * note that when coming up from TEXT, must reset the current_item[TEXT]
  1283.  * before displaying CONTEXT line, as TEXT browsing changes it....
  1284.  */
  1285.  
  1286.  
  1287. void do_ascend ()
  1288.   {
  1289.     extern int level;
  1290.     void beep();
  1291.     
  1292.     --level;
  1293.     
  1294.     if (level < INDEX)
  1295.       {
  1296.         beep ();
  1297.         printf ("Can't ascend past index display!\n");
  1298.         level = INDEX;
  1299.       }
  1300.  
  1301.     if (level == CONTEXT)
  1302.         current_item[TEXT] = get_ptr_rec (current_item[CONTEXT]);
  1303.  
  1304.     show_current_item ();
  1305.   }
  1306.  
  1307.  
  1308. /* function to jump the INDEX display to a target entered by the
  1309.  * user ... does a simple binary search (see K&R p.54) to get there
  1310.  * and then shows that line of the INDEX ... and if the
  1311.  * target word is not found, it beeps and shows the preceeding
  1312.  * word available in the index ... if called from another level, it
  1313.  * jumps to the INDEX level ...
  1314.  */
  1315.  
  1316. void do_target_jump (cmd)
  1317.   char cmd[];
  1318.   {
  1319.     register int c, diff;
  1320.     register long low, high, mid;
  1321.     KEY_REC target_item, this_item;
  1322.     extern long current_item[], max_item[];
  1323.     extern FILE *key_file;
  1324.     extern int level;
  1325.     void beep(), init_target_item();
  1326.     
  1327.     if (key_file == NULL)
  1328.       {
  1329.         beep ();
  1330.         printf ("No file open!\n");
  1331.         return;
  1332.       }
  1333.     
  1334.     level = INDEX;
  1335.     init_target_item (&target_item, cmd);
  1336.     low = min_item[INDEX];
  1337.     high = max_item[INDEX];
  1338.  
  1339.     while (low <= high)
  1340.       {
  1341.         mid = (low + high) / 2;
  1342.         get_key_rec (&this_item, mid);
  1343.         diff = zstrcmp (target_item.kkey, this_item.kkey);
  1344.         if (diff < 0)
  1345.             high = mid - 1;
  1346.         else if (diff > 0)
  1347.             low = mid + 1;
  1348.         else
  1349.           break;
  1350.       }
  1351.  
  1352.     current_item[INDEX] = mid;
  1353.     if (diff < 0)
  1354.         --current_item[INDEX];
  1355.     if (current_item[INDEX] < 0)
  1356.         current_item[INDEX] = 0;
  1357.     if (diff != 0)
  1358.         beep ();
  1359.     show_current_item ();
  1360.   }
  1361.  
  1362.  
  1363.  
  1364. /* my function to compare two strings and give a result as to who is
  1365.  * alphabetically earlier.  Note that this is almost the same as strncmp()
  1366.  * with the fixed value of KEY_LENGTH as the maximum comparison distance,
  1367.  * except that I must be sure to mask the characters to make them positive
  1368.  * (since we want to be able to handle the non-ASCII funny letters in
  1369.  * the Apple character set properly/consistently).  If the masking isn't
  1370.  * done, then inconsistent results can occur with those non-ASCII chars!
  1371.  */
  1372.  
  1373. int zstrcmp (s1, s2)
  1374.   register char *s1, *s2;
  1375.   {
  1376.     register int n = KEY_LENGTH;
  1377.     
  1378.     for (; --n && ((*s1 & 0xFF) == (*s2 & 0xFF)); s1++, s2++)
  1379.         if (!*s1) break;
  1380.         
  1381.     return ((*s1 & 0xFF) - (*s2 & 0xFF));
  1382.   }
  1383.  
  1384.  
  1385.  
  1386. /* initialize the target KEY_REC kkey string to the value typed in by
  1387.  * the user ... convert the user's string to all capital letters, and
  1388.  * pad it out to KEY_LENGTH with trailing blanks as needed ... use the
  1389.  * toupper() function warily (assume that it may be nonstandard and
  1390.  * mess up the value of non-lowercase characters, as it seems to on
  1391.  * the VAX and Sun C compilers I've tried -- so check and don't apply
  1392.  * toupper() except to lower-case characters!)....
  1393.  */
  1394.  
  1395. void init_target_item (rp, cmd)
  1396.   KEY_REC *rp;
  1397.   char cmd[];
  1398.   {
  1399.     register int c, n;
  1400.  
  1401.     strncpy (rp->kkey, "                                    ", KEY_LENGTH);
  1402.     for (n = 0; n < KEY_LENGTH && (c = cmd[n]) != 0; ++n)
  1403.         rp->kkey[n] = (islower (c) ? toupper (c) : c);
  1404.   }
  1405.  
  1406.  
  1407. /* handle the printing out of N lines due to a ".N" user command ... do
  1408.  * the job simply by marching down a step and displaying that line N times
  1409.  * ... precisely equivalent to hitting the <return> key N times.
  1410.  */
  1411.  
  1412. void do_multiprint (cmd)
  1413.   char cmd[];
  1414.   {
  1415.     register long i, n;
  1416.     extern FILE *doc_file;
  1417.     void beep();
  1418.     
  1419.     if (doc_file == NULL)
  1420.       {
  1421.         beep ();
  1422.         printf ("No file open!\n");
  1423.         return;
  1424.       }
  1425.       
  1426.     if (cmd[1] == '\0')
  1427.         strcat (cmd, "1");
  1428.     n = atol (cmd + 1);
  1429.     for (i = 0; i < n; ++i)
  1430.       {
  1431.         if (! make_move (1L))
  1432.             break;
  1433.         show_current_item ();
  1434.       }
  1435.   }
  1436.  
  1437. /* ---------------------file_utils.c--------------------- */
  1438.  
  1439. /* initialize, open, etc. various things for brwsr program...
  1440.  * 870805-13... ^z
  1441.  */
  1442.  
  1443.  
  1444. /* open the document, key, and pointer files ...
  1445.  * for now, demand that if the document file is named * then the
  1446.  * key file must be named *.k and the pointer file must be *.p
  1447.  * ... just the way that the ndxr program builds the index.....
  1448.  */
  1449.  
  1450. void open_files (cmd)
  1451.   char cmd[];
  1452.   {
  1453.     char ocmd[STRLEN], *strcat(), *strcpy();
  1454.     FILE *fopen();
  1455.     extern FILE *doc_file, *key_file, *ptr_file;
  1456.     void beep();
  1457.  
  1458.     close_files ();
  1459.     strcpy (ocmd, cmd + 1);
  1460.     if ((doc_file = fopen (ocmd, "r")) == NULL)
  1461.       {
  1462.         beep ();
  1463.         printf ("Error opening document file \"%s\"!\n", ocmd);
  1464.         close_files ();
  1465.         return;
  1466.       }
  1467.  
  1468.     strcat (ocmd, ".k");
  1469.     if ((key_file = fopen (ocmd, "rb")) == NULL)
  1470.       {
  1471.         beep ();
  1472.         printf ("Error opening key file \"%s\"!\n", ocmd);
  1473.         close_files ();
  1474.         return;
  1475.       }
  1476.  
  1477.     ocmd[strlen (ocmd) - 2] = '\0';
  1478.     strcat (ocmd, ".p");
  1479.     if ((ptr_file = fopen (ocmd, "rb")) == NULL)
  1480.       {
  1481.         beep ();
  1482.         printf ("Error opening ptr file \"%s\"!\n", ocmd);
  1483.         close_files ();
  1484.         return;
  1485.       }
  1486.   }
  1487.  
  1488.  
  1489. /* close off the document, key, and pointer files
  1490.  */
  1491.  
  1492. void close_files ()
  1493.   {
  1494.     extern FILE *doc_file, *key_file, *ptr_file;
  1495.     
  1496.     if (doc_file != NULL)
  1497.       {
  1498.         fclose (doc_file);
  1499.         doc_file = NULL;
  1500.       }
  1501.     if (key_file != NULL)
  1502.       {
  1503.         fclose (key_file);
  1504.         key_file = NULL;
  1505.       }
  1506.     if (ptr_file != NULL)
  1507.       {
  1508.         fclose (ptr_file);
  1509.         ptr_file = NULL;
  1510.       }
  1511.   }
  1512.  
  1513.  
  1514. /* set up initial values for current_item and max_item arrays ...
  1515.  * initially, current_item is set to an illegal value, -1, and
  1516.  * max_item[CONTEXT] and max_item[TEXT] convey information about
  1517.  * the sizes of the pointer and document files respectively...
  1518.  */
  1519.  
  1520. void init_items ()
  1521.   {
  1522.     extern long current_item[], max_item[];
  1523.     extern FILE *doc_file, *key_file, *ptr_file;
  1524.     extern int level;
  1525.     
  1526.     level = INDEX;
  1527.     
  1528.     current_item[INDEX] = -1;
  1529.     current_item[CONTEXT] = -1;
  1530.     current_item[TEXT] = -1;
  1531.  
  1532.     fseek (key_file, 0L, 2);
  1533.     max_item[INDEX] = ftell (key_file) / sizeof (KEY_REC) - 1;
  1534.     
  1535.     fseek (ptr_file, 0L, 2);
  1536.     max_item[CONTEXT] = ftell (ptr_file) / sizeof (PTR_REC) - 1;
  1537.  
  1538.     fseek (doc_file, 0L, 2);
  1539.     max_item[TEXT] = ftell (doc_file) - 1;
  1540.   }
  1541.  
  1542.  
  1543. /* --------------------show_items.c------------------ */
  1544.  
  1545. /* functions to show a chosen item from an index key file,etc.
  1546.  * 870805-13...  ^z
  1547.  */
  1548.  
  1549.  
  1550. /* function to show whatever the current item is, in whatever level
  1551.  * the user is at the moment.... basically, it just routes the
  1552.  * request to whichever is the appropriate agent to take care of
  1553.  * it, for INDEX, CONTEXT, or TEXT levels....
  1554.  */
  1555.  
  1556. void show_current_item ()
  1557.   {
  1558.     extern int level;
  1559.     extern FILE *doc_file;
  1560.     void beep(), show_current_index_item(), show_current_context_item(),
  1561.         show_current_text_item();
  1562.     
  1563.     if (doc_file == NULL)
  1564.       {
  1565.         beep ();
  1566.         printf ("No file open!\n");
  1567.         return;
  1568.       }
  1569.     
  1570.     if (level == INDEX)
  1571.         show_current_index_item ();
  1572.     else if (level == CONTEXT)
  1573.         show_current_context_item ();
  1574.     else
  1575.         show_current_text_item ();
  1576.   }
  1577.  
  1578.  
  1579. /* function to show the current INDEX item (count of occurrences followed
  1580.  * by the indexed word itself) ... send copy to notes file, if open....
  1581.  */
  1582.  
  1583. void show_current_index_item ()
  1584.   {
  1585.     extern KEY_REC this_rec, prev_rec;
  1586.     extern long current_item[], subset_rec_count;
  1587.     extern FILE *notes_file;
  1588.     extern char *subset;
  1589.     long count_subset_recs ();
  1590.     void get_key_rec ();
  1591.  
  1592.     get_key_rec (&prev_rec, current_item[INDEX] - 1);
  1593.     get_key_rec (&this_rec, current_item[INDEX]);
  1594.     
  1595.     if (subset == NULL)
  1596.       {
  1597.         printf ("%6ld %.28s\n", this_rec.ccount - prev_rec.ccount,
  1598.                     this_rec.kkey);
  1599.         if (notes_file != NULL)
  1600.             fprintf (notes_file, "%6ld %.28s\n",
  1601.                     this_rec.ccount - prev_rec.ccount, this_rec.kkey);
  1602.       }
  1603.     else
  1604.       {
  1605.           subset_rec_count = count_subset_recs (&this_rec, &prev_rec);
  1606.         printf ("%6ld/%-6ld %.28s\n", subset_rec_count,
  1607.                     this_rec.ccount - prev_rec.ccount, this_rec.kkey);
  1608.         if (notes_file != NULL)
  1609.         fprintf (notes_file, "%6ld/%-6ld %.28s\n", subset_rec_count,
  1610.                     this_rec.ccount - prev_rec.ccount, this_rec.kkey);
  1611.       }
  1612.   }
  1613.  
  1614.  
  1615. /* function to show the current CONTEXT item, a line in the form of a
  1616.  * key-word-in-context (KWIC) display with the indexed term in the'
  1617.  * middle ... the line is filtered so that the presence of tabs, <cr>'s,
  1618.  * or other nasty control characters won't cause any problems... 
  1619.  * a little testing is needed to take care of end-effects, so that 
  1620.  * nothing happens for context lines near either end of the document
  1621.  * file ... finally, send a copy to the notes file, if open....
  1622.  */
  1623.  
  1624. void show_current_context_item ()
  1625.   {
  1626.     register int n, m;
  1627.     register long p;
  1628.     char buf[CONTEXT_LINE_LENGTH + 1];
  1629.     extern long current_item[];
  1630.     extern FILE *doc_file;
  1631.     void beep();
  1632.     
  1633.     p = current_item[TEXT] - CONTEXT_OFFSET;
  1634.     n = 0;
  1635.     if (p < 0)
  1636.       {
  1637.         for ( ; n < -p; ++n)
  1638.             buf[n] = ' ';
  1639.         p = 0;
  1640.       }
  1641.     if (fseek (doc_file, p, 0) != 0)
  1642.           {
  1643.             beep ();
  1644.             printf ("Error in fseek() getting a context line to show!\n");
  1645.             return;
  1646.           }
  1647.     if ((m = fread (buf + n, sizeof (char), CONTEXT_LINE_LENGTH - n,
  1648.                         doc_file)) == 0)
  1649.           {
  1650.             beep ();
  1651.             printf ("Error in fread() getting a context line to show!\n");
  1652.             return;
  1653.           }
  1654.     n += m;
  1655.     for ( ; n < CONTEXT_LINE_LENGTH; ++n)
  1656.         buf[n] = ' ';
  1657.     for (n = 0; n < CONTEXT_LINE_LENGTH; ++n)
  1658.         if (! isprint (buf[n] & 0xFF))
  1659.             buf[n] = ' ';
  1660.     buf[CONTEXT_LINE_LENGTH] = '\0';
  1661.     printf ("%s\n", buf);
  1662.     if (notes_file != NULL)
  1663.         fprintf (notes_file, "%s\n", buf);
  1664.   }
  1665.  
  1666.  
  1667. /* function to show the current TEXT item, a line from the document file
  1668.  * itself ... copy to notes file also, if so desired....
  1669.  */
  1670.  
  1671. void show_current_text_item ()
  1672.   {
  1673.     char buf[STRLEN + 1];
  1674.     extern long current_item[];
  1675.     extern FILE *doc_file;
  1676.     void beep();
  1677.     
  1678.     if (fseek (doc_file, current_item[TEXT], 0) != 0)
  1679.           {
  1680.             beep ();
  1681.             printf ("Error in fseek() getting a text line to show!\n");
  1682.             return;
  1683.           }
  1684.     if (fgets (buf, STRLEN, doc_file) == NULL)
  1685.           {
  1686.             beep ();
  1687.             printf ("Error in fgets() getting a text line to show!\n");
  1688.             return;
  1689.           }
  1690.     printf ("%s", buf);
  1691.     if (notes_file != NULL)
  1692.             fprintf (notes_file, "%s", buf);
  1693.   }
  1694.  
  1695.  
  1696.